/*
 * jquery.layout 1.2.0
 *
 * Copyright (c) 2008 
 *   Fabrizio Balliano (http://www.fabrizioballiano.net)
 *   Kevin Dalman (http://allpro.net)
 *
 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
 *
 * $Date: 2008-12-27 02:17:22 +0100 (sab, 27 dic 2008) $
 * $Rev: 203 $
 * 
 * NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars
 */
(function($) {

    $.fn.layout = function (opts) {

        /*
 * ###########################
 *   WIDGET CONFIG & OPTIONS
 * ###########################
 */

        // DEFAULTS for options
        var 
        prefix = "ui-layout-" // prefix for ALL selectors and classNames
        ,	defaults = { //	misc default values
            paneClass:				prefix+"pane"		// ui-layout-pane
            ,	
            resizerClass:			prefix+"resizer"	// ui-layout-resizer
            ,	
            togglerClass:			prefix+"toggler"	// ui-layout-toggler
            ,	
            togglerInnerClass:		prefix+""			// ui-layout-open / ui-layout-closed
            ,	
            buttonClass:			prefix+"button"		// ui-layout-button
            ,	
            contentSelector:		"."+prefix+"content"// ui-layout-content
            ,	
            contentIgnoreSelector:	"."+prefix+"ignore"	// ui-layout-mask 
        }
        ;

        // DEFAULT PANEL OPTIONS - CHANGE IF DESIRED
        var options = {
            name:						""			// FUTURE REFERENCE - not used right now
            ,	
            scrollToBookmarkOnLoad:		true		// after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
            ,	
            defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings'
                applyDefaultStyles: 	false		// apply basic styles directly to resizers & buttons? If not, then stylesheet must handle it
                ,	
                closable:				true		// pane can open & close
                ,	
                resizable:				true		// when open, pane can be resized 
                ,	
                slidable:				true		// when closed, pane can 'slide' open over other panes - closes on mouse-out
                //,	paneSelector:			[ ]			// MUST be pane-specific!
                ,	
                contentSelector:		defaults.contentSelector	// INNER div/element to auto-size so only it scrolls, not the entire pane!
                ,	
                contentIgnoreSelector:	defaults.contentIgnoreSelector	// elem(s) to 'ignore' when measuring 'content'
                ,	
                paneClass:				defaults.paneClass		// border-Pane - default: 'ui-layout-pane'
                ,	
                resizerClass:			defaults.resizerClass	// Resizer Bar		- default: 'ui-layout-resizer'
                ,	
                togglerClass:			defaults.togglerClass	// Toggler Button	- default: 'ui-layout-toggler'
                ,	
                buttonClass:			defaults.buttonClass	// CUSTOM Buttons	- default: 'ui-layout-button-toggle/-open/-close/-pin'
                ,	
                resizerDragOpacity:		1			// option for ui.draggable
                //,	resizerCursor:			""			// MUST be pane-specific - cursor when over resizer-bar
                ,	
                maskIframesOnResize:	true		// true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging
                //,	size:					100			// inital size of pane - defaults are set 'per pane'
                ,	
                minSize:				0			// when manually resizing a pane
                ,	
                maxSize:				0			// ditto, 0 = no limit
                ,	
                spacing_open:			6			// space between pane and adjacent panes - when pane is 'open'
                ,	
                spacing_closed:			6			// ditto - when pane is 'closed'
                ,	
                togglerLength_open:		50			// Length = WIDTH of toggler button on north/south edges - HEIGHT on east/west edges
                ,	
                togglerLength_closed: 	50			// 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
                ,	
                togglerAlign_open:		"center"	// top/left, bottom/right, center, OR...
                ,	
                togglerAlign_closed:	"center"	// 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
                ,	
                togglerTip_open:		"Close"		// Toggler tool-tip (title)
                ,	
                togglerTip_closed:		"Open"		// ditto
                ,	
                resizerTip:				"Resize"	// Resizer tool-tip (title)
                ,	
                sliderTip:				"Slide Open" // resizer-bar triggers 'sliding' when pane is closed
                ,	
                sliderCursor:			"pointer"	// cursor when resizer-bar will trigger 'sliding'
                ,	
                slideTrigger_open:		"click"		// click, dblclick, mouseover
                ,	
                slideTrigger_close:		"mouseout"	// click, mouseout
                ,	
                hideTogglerOnSlide:		false		// when pane is slid-open, should the toggler show?
                ,	
                togglerContent_open:	""			// text or HTML to put INSIDE the toggler
                ,	
                togglerContent_closed:	""			// ditto
                ,	
                showOverflowOnHover:	false		// will bind allowOverflow() utility to pane.onMouseOver
                ,	
                enableCursorHotkey:		true		// enabled 'cursor' hotkeys
                //,	customHotkey:			""			// MUST be pane-specific - EITHER a charCode OR a character
                ,	
                customHotkeyModifier:	"SHIFT"		// either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
                //	NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed
                ,	
                fxName:					"slide" 	// ('none' or blank), slide, drop, scale
                ,	
                fxSpeed:				null		// slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
                ,	
                fxSettings:				{}			// can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
                ,	
                initClosed:				false		// true = init pane as 'closed'
                ,	
                initHidden: 			false 		// true = init pane as 'hidden' - no resizer or spacing
		
            /*	callback options do not have to be set - listed here for reference only
		,	onshow_start:			""			// CALLBACK when pane STARTS to Show	- BEFORE onopen/onhide_start
		,	onshow_end:				""			// CALLBACK when pane ENDS being Shown	- AFTER  onopen/onhide_end
		,	onhide_start:			""			// CALLBACK when pane STARTS to Close	- BEFORE onclose_start
		,	onhide_end:				""			// CALLBACK when pane ENDS being Closed	- AFTER  onclose_end
		,	onopen_start:			""			// CALLBACK when pane STARTS to Open
		,	onopen_end:				""			// CALLBACK when pane ENDS being Opened
		,	onclose_start:			""			// CALLBACK when pane STARTS to Close
		,	onclose_end:			""			// CALLBACK when pane ENDS being Closed
		,	onresize_start:			""			// CALLBACK when pane STARTS to be ***MANUALLY*** Resized
		,	onresize_end:			""			// CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
		*/
            }
            ,	
            north: {
                paneSelector:			"."+prefix+"north" // default = .ui-layout-north
                ,	
                size:					"auto"
                ,	
                resizerCursor:			"n-resize"
            }
            ,	
            south: {
                paneSelector:			"."+prefix+"south" // default = .ui-layout-south
                ,	
                size:					"auto"
                ,	
                resizerCursor:			"s-resize"
            }
            ,	
            east: {
                paneSelector:			"."+prefix+"east" // default = .ui-layout-east
                ,	
                size:					200
                ,	
                resizerCursor:			"e-resize"
            }
            ,	
            west: {
                paneSelector:			"."+prefix+"west" // default = .ui-layout-west
                ,	
                size:					200
                ,	
                resizerCursor:			"w-resize"
            }
            ,	
            center: {
                paneSelector:			"."+prefix+"center" // default = .ui-layout-center
            }

        };


        var effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings
            slide:	{
                all:	{
                    duration:  "fast"
                } // eg: duration: 1000, easing: "easeOutBounce"
                ,	
                north:	{
                    direction: "up"
                }
                ,	
                south:	{
                    direction: "down"
                }
                ,	
                east:	{
                    direction: "right"
                }
                ,	
                west:	{
                    direction: "left"
                }
            }
            ,	
            drop:	{
                all:	{
                    duration:  "slow"
                } // eg: duration: 1000, easing: "easeOutQuint"
                ,	
                north:	{
                    direction: "up"
                }
                ,	
                south:	{
                    direction: "down"
                }
                ,	
                east:	{
                    direction: "right"
                }
                ,	
                west:	{
                    direction: "left"
                }
            }
            ,	
            scale:	{
                all:	{
                    duration:  "fast"
                }
            }
        };


        // STATIC, INTERNAL CONFIG - DO NOT CHANGE THIS!
        var config = {
            allPanes:		"north,south,east,west,center"
            ,	
            borderPanes:	"north,south,east,west"
            ,	
            zIndex: { // set z-index values here
                resizer_normal:	1		// normal z-index for resizer-bars
                ,	
                pane_normal:	2		// normal z-index for panes
                ,	
                mask:			4		// overlay div used to mask pane(s) during resizing
                ,	
                sliding:		100		// applied to both the pane and its resizer when a pane is 'slid open'
                ,	
                resizing:		10000	// applied to the CLONED resizer-bar when being 'dragged'
                ,	
                animation:		10000	// applied to the pane when being animated - not applied to the resizer
            }
            ,	
            resizers: {
                cssReq: {
                    position: 	"absolute"
                    ,	
                    padding: 	0
                    ,	
                    margin: 	0
                    ,	
                    fontSize:	"1px"
                    ,	
                    textAlign:	"left" // to counter-act "center" alignment!
                    ,	
                    overflow: 	"hidden" // keep toggler button from overflowing
                    ,	
                    zIndex: 	1
                }
                ,	
                cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true
                    background: "#DDD"
                    ,	
                    border:		"none"
                }
            }
            ,	
            togglers: {
                cssReq: {
                    position: 	"absolute"
                    ,	
                    display: 	"block"
                    ,	
                    padding: 	0
                    ,	
                    margin: 	0
                    ,	
                    overflow:	"hidden"
                    ,	
                    textAlign:	"center"
                    ,	
                    fontSize:	"1px"
                    ,	
                    cursor: 	"pointer"
                    ,	
                    zIndex: 	1
                }
                ,	
                cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true
                    background: "#AAA"
                }
            }
            ,	
            content: {
                cssReq: {
                    overflow:	"auto"
                }
                ,	
                cssDef: {}
            }
            ,	
            defaults: { // defaults for ALL panes - overridden by 'per-pane settings' below
                cssReq: {
                    position: 	"absolute"
                    ,	
                    margin:		0
                    ,	
                    zIndex: 	2
                }
                ,	
                cssDef: {
                    padding:	"10px"
                    ,	
                    background:	"#FFF"
                    ,	
                    border:		"1px solid #BBB"
                    ,	
                    overflow:	"auto"
                }
            }
            ,	
            north: {
                edge:			"top"
                ,	
                sizeType:		"height"
                ,	
                dir:			"horz"
                ,	
                cssReq: {
                    top: 		0
                    ,	
                    bottom: 	"auto"
                    ,	
                    left: 		0
                    ,	
                    right: 		0
                    ,	
                    width: 		"auto"
                //	height: 	DYNAMIC
                }
            }
            ,	
            south: {
                edge:			"bottom"
                ,	
                sizeType:		"height"
                ,	
                dir:			"horz"
                ,	
                cssReq: {
                    top: 		"auto"
                    ,	
                    bottom: 	0
                    ,	
                    left: 		0
                    ,	
                    right: 		0
                    ,	
                    width: 		"auto"
                //	height: 	DYNAMIC
                }
            }
            ,	
            east: {
                edge:			"right"
                ,	
                sizeType:		"width"
                ,	
                dir:			"vert"
                ,	
                cssReq: {
                    left: 		"auto"
                    ,	
                    right: 		0
                    ,	
                    top: 		"auto" // DYNAMIC
                    ,	
                    bottom: 	"auto" // DYNAMIC
                    ,	
                    height: 	"auto"
                //	width: 		DYNAMIC
                }
            }
            ,	
            west: {
                edge:			"left"
                ,	
                sizeType:		"width"
                ,	
                dir:			"vert"
                ,	
                cssReq: {
                    left: 		0
                    ,	
                    right: 		"auto"
                    ,	
                    top: 		"auto" // DYNAMIC
                    ,	
                    bottom: 	"auto" // DYNAMIC
                    ,	
                    height: 	"auto"
                //	width: 		DYNAMIC
                }
            }
            ,	
            center: {
                dir:			"center"
                ,	
                cssReq: {
                    left: 		"auto" // DYNAMIC
                    ,	
                    right: 		"auto" // DYNAMIC
                    ,	
                    top: 		"auto" // DYNAMIC
                    ,	
                    bottom: 	"auto" // DYNAMIC
                    ,	
                    height: 	"auto"
                    ,	
                    width: 		"auto"
                }
            }
        };


        // DYNAMIC DATA
        var state = {
            // generate random 'ID#' to identify layout - used to create global namespace for timers
            id:			Math.floor(Math.random() * 10000)
            ,	
            container:	{}
            ,	
            north:		{}
            ,	
            south:		{}
            ,	
            east:		{}
            ,	
            west:		{}
            ,	
            center:		{}
        };


        var 
        altEdge = {
            top:	"bottom"
            ,	
            bottom: "top"
            ,	
            left:	"right"
            ,	
            right:	"left"
        }
        ,	altSide = {
            north:	"south"
            ,	
            south:	"north"
            ,	
            east: 	"west"
            ,	
            west: 	"east"
        }
        ;


        /*
 * ###########################
 *  INTERNAL HELPER FUNCTIONS
 * ###########################
 */

        /**
	 * isStr
	 *
	 * Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false
	 */
        var isStr = function (o) {
            if (typeof o == "string")
                return true;
            else if (typeof o == "object") {
                try {
                    var match = o.constructor.toString().match(/string/i); 
                    return (match !== null);
                } catch (e) {} 
            }
            return false;
        };

        /**
	 * str
	 *
	 * Returns a simple string if the passed param is EITHER a simple string OR a 'string object',
	 *  else returns the original object
	 */
        var str = function (o) {
            if (typeof o == "string" || isStr(o)) return $.trim(o); // trim converts 'String object' to a simple string
            else return o;
        };

        /**
	 * min / max
	 *
	 * Alias for Math.min/.max to simplify coding
	 */
        var min = function (x,y) {
            return Math.min(x,y);
        };
        var max = function (x,y) {
            return Math.max(x,y);
        };

        /**
	 * transformData
	 *
	 * Processes the options passed in and transforms them into the format used by layout()
	 * Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys)
	 * In flat-format, pane-specific-settings are prefixed like: north__optName  (2-underscores)
	 * To update effects, options MUST use nested-keys format, with an effects key
	 *
	 * @callers  initOptions()
	 * @params  JSON  d  Data/options passed by user - may be a single level or nested levels
	 * @returns JSON  Creates a data struture that perfectly matches 'options', ready to be imported
	 */
        var transformData = function (d) {
            var json = {
                defaults:{
                    fxSettings:{}
                }, 
                north:{
                    fxSettings:{}
                }, 
                south:{
                    fxSettings:{}
                }, 
                east:{
                    fxSettings:{}
                }, 
                west:{
                    fxSettings:{}
                }, 
                center:{
                    fxSettings:{}
                }
            };
            d = d || {};
            if (d.effects || d.defaults || d.north || d.south || d.west || d.east || d.center)
                json = $.extend( json, d ); // already in json format - add to base keys
            else
                // convert 'flat' to 'nest-keys' format - also handles 'empty' user-options
                $.each( d, function (key,val) {
                    a = key.split("__");
                    json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val;
                });
            return json;
        };

        /**
	 * setFlowCallback
	 *
	 * Set an INTERNAL callback to avoid simultaneous animation
	 * Runs only if needed and only if all callbacks are not 'already set'!
	 *
	 * @param String   action  Either 'open' or 'close'
	 * @pane  String   pane    A valid border-pane name, eg 'west'
	 * @pane  Boolean  param   Extra param for callback (optional)
	 */
        var setFlowCallback = function (action, pane, param) {
            var
            cb = action +","+ pane +","+ (param ? 1 : 0)
            ,	cP, cbPane
            ;
            $.each(c.borderPanes.split(","), function (i,p) {
                if (c[p].isMoving) {
                    bindCallback(p); // TRY to bind a callback
                    return false; // BREAK
                }
            });

            function bindCallback (p, test) {
                cP = c[p];
                if (!cP.doCallback) {
                    cP.doCallback = true;
                    cP.callback = cb;
                }
                else { // try to 'chain' this callback
                    cpPane = cP.callback.split(",")[1]; // 2nd param is 'pane'
                    if (cpPane != p && cpPane != pane) // callback target NOT 'itself' and NOT 'this pane'
                        bindCallback (cpPane, true); // RECURSE
                }
            }
        };

        /**
	 * execFlowCallback
	 *
	 * RUN the INTERNAL callback for this pane - if one exists
	 *
	 * @param String   action  Either 'open' or 'close'
	 * @pane  String   pane    A valid border-pane name, eg 'west'
	 * @pane  Boolean  param   Extra param for callback (optional)
	 */
        var execFlowCallback = function (pane) {
            var cP = c[pane];

            // RESET flow-control flaGs
            c.isLayoutBusy = false;
            delete cP.isMoving;
            if (!cP.doCallback || !cP.callback) return;

            cP.doCallback = false; // RESET logic flag

            // EXECUTE the callback
            var
            cb = cP.callback.split(",")
            ,	param = (cb[2] > 0 ? true : false)
            ;
            if (cb[0] == "open")
                open( cb[1], param  );
            else if (cb[0] == "close")
                close( cb[1], param );

            if (!cP.doCallback) cP.callback = null; // RESET - unless callback above enabled it again!
        };

        /**
	 * execUserCallback
	 *
	 * Executes a Callback function after a trigger event, like resize, open or close
	 *
	 * @param String  pane   This is passed only so we can pass the 'pane object' to the callback
	 * @param String  v_fn  Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument
	 */
        var execUserCallback = function (pane, v_fn) {
            if (!v_fn) return;
            var fn;
            try {
                if (typeof v_fn == "function")
                    fn = v_fn;	
                else if (typeof v_fn != "string")
                    return;
                else if (v_fn.indexOf(",") > 0) {
                    // function name cannot contain a comma, so must be a function name AND a 'name' parameter
                    var
                    args = v_fn.split(",")
                    ,	fn = eval(args[0])
                    ;
                    if (typeof fn=="function" && args.length > 1)
                        return fn(args[1]); // pass the argument parsed from 'list'
                }
                else // just the name of an external function?
                    fn = eval(v_fn);

                if (typeof fn=="function")
                    // pass data: pane-name, pane-element, pane-state, pane-options, and layout-name
                    return fn( pane, $Ps[pane], $.extend({},state[pane]), $.extend({},options[pane]), options.name );
            }
            catch (ex) {}
        };

        /**
	 * cssNum
	 *
	 * Returns the 'current CSS value' for an element - returns 0 if property does not exist
	 *
	 * @callers  Called by many methods
	 * @param jQuery  $Elem  Must pass a jQuery object - first element is processed
	 * @param String  property  The name of the CSS property, eg: top, width, etc.
	 * @returns Variant  Usually is used to get an integer value for position (top, left) or size (height, width)
	 */
        var cssNum = function ($E, prop) {
            var
            val = 0
            ,	hidden = false
            ,	visibility = ""
            ;
            if (!$.browser.msie) { // IE CAN read dimensions of 'hidden' elements - FF CANNOT
                if ($.curCSS($E[0], "display", true) == "none") {
                    hidden = true;
                    visibility = $.curCSS($E[0], "visibility", true); // SAVE current setting
                    $E.css({
                        display: "block", 
                        visibility: "hidden"
                    }); // show element 'invisibly' so we can measure it
                }
            }

            val = parseInt($.curCSS($E[0], prop, true), 10) || 0;

            if (hidden) { // WAS hidden, so put back the way it was
                $E.css({
                    display: "none"
                });
                if (visibility && visibility != "hidden")
                    $E.css({
                        visibility: visibility
                    }); // reset 'visibility'
            }

            return val;
        };

        /**
	 * cssW / cssH / cssSize
	 *
	 * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
	 *
	 * @callers  initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
	 * @param Variant  elem  Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
	 * @param Integer  outerWidth/outerHeight  (optional) Can pass a width, allowing calculations BEFORE element is resized
	 * @returns Integer  Returns the innerHeight of the elem by subtracting padding and borders
	 *
	 * @TODO  May need to add additional logic to handle more browser/doctype variations?
	 */
        var cssW = function (e, outerWidth) {
            var $E;
            if (isStr(e)) {
                e = str(e);
                $E = $Ps[e];
            }
            else
                $E = $(e);

            // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
            if (outerWidth <= 0)
                return 0;
            else if (!(outerWidth>0))
                outerWidth = isStr(e) ? getPaneSize(e) : $E.outerWidth();

            if (!$.boxModel)
                return outerWidth;

            else // strip border and padding size from outerWidth to get CSS Width
                return outerWidth
                - cssNum($E, "paddingLeft")		
                - cssNum($E, "paddingRight")
                - ($.curCSS($E[0], "borderLeftStyle", true) == "none" ? 0 : cssNum($E, "borderLeftWidth"))
                - ($.curCSS($E[0], "borderRightStyle", true) == "none" ? 0 : cssNum($E, "borderRightWidth"))
        ;
        };
        var cssH = function (e, outerHeight) {
            var $E;
            if (isStr(e)) {
                e = str(e);
                $E = $Ps[e];
            }
            else
                $E = $(e);

            // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
            if (outerHeight <= 0)
                return 0;
            else if (!(outerHeight>0))
                outerHeight = (isStr(e)) ? getPaneSize(e) : $E.outerHeight();

            if (!$.boxModel)
                return outerHeight;

            else // strip border and padding size from outerHeight to get CSS Height
                return outerHeight
                - cssNum($E, "paddingTop")
                - cssNum($E, "paddingBottom")
                - ($.curCSS($E[0], "borderTopStyle", true) == "none" ? 0 : cssNum($E, "borderTopWidth"))
                - ($.curCSS($E[0], "borderBottomStyle", true) == "none" ? 0 : cssNum($E, "borderBottomWidth"))
        ;
        };
        var cssSize = function (pane, outerSize) {
            if (c[pane].dir=="horz") // pane = north or south
                return cssH(pane, outerSize);
            else // pane = east or west
                return cssW(pane, outerSize);
        };

        /**
	 * getPaneSize
	 *
	 * Calculates the current 'size' (width or height) of a border-pane - optionally with 'pane spacing' added
	 *
	 * @returns Integer  Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser
	 */
        var getPaneSize = function (pane, inclSpace) {
            var 
            $P	= $Ps[pane]
            ,	o	= options[pane]
            ,	s	= state[pane]
            ,	oSp	= (inclSpace ? o.spacing_open : 0)
            ,	cSp	= (inclSpace ? o.spacing_closed : 0)
            ;
            if (!$P || s.isHidden)
                return 0;
            else if (s.isClosed || (s.isSliding && inclSpace))
                return cSp;
            else if (c[pane].dir == "horz")
                return $P.outerHeight() + oSp;
            else // dir == "vert"
                return $P.outerWidth() + oSp;
        };

        var setPaneMinMaxSizes = function (pane) {
            var 
            d				= cDims
            ,	edge			= c[pane].edge
            ,	dir				= c[pane].dir
            ,	o				= options[pane]
            ,	s				= state[pane]
            ,	$P				= $Ps[pane]
            ,	$altPane		= $Ps[ altSide[pane] ]
            ,	paneSpacing		= o.spacing_open
            ,	altPaneSpacing	= options[ altSide[pane] ].spacing_open
            ,	altPaneSize		= (!$altPane ? 0 : (dir=="horz" ? $altPane.outerHeight() : $altPane.outerWidth()))
            ,	containerSize	= (dir=="horz" ? d.innerHeight : d.innerWidth)
            //	limitSize prevents this pane from 'overlapping' opposite pane - even if opposite pane is currently closed
            ,	limitSize		= containerSize - paneSpacing - altPaneSize - altPaneSpacing
            ,	minSize			= s.minSize || 0
            ,	maxSize			= Math.min(s.maxSize || 9999, limitSize)
            ,	minPos, maxPos	// used to set resizing limits
            ;
            switch (pane) {
                case "north":
                    minPos = d.offsetTop + minSize;
                    maxPos = d.offsetTop + maxSize;
                    break;
                case "west":
                    minPos = d.offsetLeft + minSize;
                    maxPos = d.offsetLeft + maxSize;
                    break;
                case "south":
                    minPos = d.offsetTop + d.innerHeight - maxSize;
                    maxPos = d.offsetTop + d.innerHeight - minSize;
                    break;
                case "east":
                    minPos = d.offsetLeft + d.innerWidth - maxSize;
                    maxPos = d.offsetLeft + d.innerWidth - minSize;
                    break;
            }
            // save data to pane-state
            $.extend(s, {
                minSize: minSize, 
                maxSize: maxSize, 
                minPosition: minPos, 
                maxPosition: maxPos
            });
        };

        /**
	 * getPaneDims
	 *
	 * Returns data for setting the size/position of center pane. Date is also used to set Height for east/west panes
	 *
	 * @returns JSON  Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
	 */
        var getPaneDims = function () {
            var d = {
                top:	getPaneSize("north", true) // true = include 'spacing' value for p
                ,	
                bottom:	getPaneSize("south", true)
                ,	
                left:	getPaneSize("west", true)
                ,	
                right:	getPaneSize("east", true)
                ,	
                width:	0
                ,	
                height:	0
            };

            with (d) {
                width 	= cDims.innerWidth - left - right;
                height 	= cDims.innerHeight - bottom - top;
                // now add the 'container border/padding' to get final positions - relative to the container
                top		+= cDims.top;
                bottom	+= cDims.bottom;
                left	+= cDims.left;
                right	+= cDims.right;
                }

            return d;
        };


        /**
	 * getElemDims
	 *
	 * Returns data for setting size of an element (container or a pane).
	 *
	 * @callers  create(), onWindowResize() for container, plus others for pane
	 * @returns JSON  Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
	 */
        var getElemDims = function ($E) {
            var
            d = {} // dimensions hash
            ,	e, b, p // edge, border, padding
            ;

            $.each("Left,Right,Top,Bottom".split(","), function () {
                e = str(this);
                b = d["border" +e] = cssNum($E, "border"+e+"Width");
                p = d["padding"+e] = cssNum($E, "padding"+e);
                d["offset" +e] = b + p; // total offset of content from outer edge
                // if BOX MODEL, then 'position' = PADDING (ignore borderWidth)
                if ($E == $Container)
                    d[e.toLowerCase()] = ($.boxModel ? p : 0); 
            });

            d.innerWidth  = d.outerWidth  = $E.outerWidth();
            d.innerHeight = d.outerHeight = $E.outerHeight();
            if ($.boxModel) {
                d.innerWidth  -= (d.offsetLeft + d.offsetRight);
                d.innerHeight -= (d.offsetTop  + d.offsetBottom);
            }

            return d;
        };


        var setTimer = function (pane, action, fn, ms) {
            var
            Layout = window.layout = window.layout || {}
            ,	Timers = Layout.timers = Layout.timers || {}
            ,	name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action
            ;
            if (Timers[name]) return; // timer already set!
            else Timers[name] = setTimeout(fn, ms);
        };

        var clearTimer = function (pane, action) {
            var
            Layout = window.layout = window.layout || {}
            ,	Timers = Layout.timers = Layout.timers || {}
            ,	name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action
            ;
            if (Timers[name]) {
                clearTimeout( Timers[name] );
                delete Timers[name];
                return true;
            }
            else
                return false;
        };


        /*
 * ###########################
 *   INITIALIZATION METHODS
 * ###########################
 */

        /**
	 * create
	 *
	 * Initialize the layout - called automatically whenever an instance of layout is created
	 *
	 * @callers  NEVER explicity called
	 * @returns  An object pointer to the instance created
	 */
        var create = function () {
            // initialize config/options
            initOptions();

            // initialize all objects
            initContainer();	// set CSS as needed and init state.container dimensions
            initPanes();		// size & position all panes
            initHandles();		// create and position all resize bars & togglers buttons
            initResizable();	// activate resizing on all panes where resizable=true
            sizeContent("all");	// AFTER panes & handles have been initialized, size 'content' divs

            if (options.scrollToBookmarkOnLoad)
                with (self.location) if (hash) replace( hash ); // scrollTo Bookmark

            // bind hotkey function - keyDown - if required
            initHotkeys();

            // bind resizeAll() for 'this layout instance' to window.resize event
            $(window).resize(function () {
                var timerID = "timerLayout_"+state.id;
                if (window[timerID]) clearTimeout(window[timerID]);
                window[timerID] = null;
                if (true || $.browser.msie) // use a delay for IE because the resize event fires repeatly
                    window[timerID] = setTimeout(resizeAll, 100);
                else // most other browsers have a built-in delay before firing the resize event
                    resizeAll(); // resize all layout elements NOW!
            });
        };

        /**
	 * initContainer
	 *
	 * Validate and initialize container CSS and events
	 *
	 * @callers  create()
	 */
        var initContainer = function () {
            try { // format html/body if this is a full page layout
                if ($Container[0].tagName == "BODY") {
                    $("html").css({
                        height:		"100%"
                        ,	
                        overflow:	"hidden"
                    });
                    $("body").css({
                        position:	"relative"
                        ,	
                        height:		"100%"
                        ,	
                        overflow:	"hidden"
                        ,	
                        margin:		0
                        ,	
                        padding:	0		// TODO: test whether body-padding could be handled?
                        ,	
                        border:		"none"	// a body-border creates problems because it cannot be measured!
                    });
                }
                else { // set required CSS - overflow and position
                    var
                    CSS	= {
                        overflow: "hidden"
                    } // make sure container will not 'scroll'
                    ,	p	= $Container.css("position")
                    ,	h	= $Container.css("height")
                    ;
                    // if this is a NESTED layout, then outer-pane ALREADY has position and height
                    if (!$Container.hasClass("ui-layout-pane")) {
                        if (!p || "fixed,absolute,relative".indexOf(p) < 0)
                            CSS.position = "relative"; // container MUST have a 'position'
                        if (!h || h=="auto")
                            CSS.height = "100%"; // container MUST have a 'height'
                    }
                    $Container.css( CSS );
                }
            } catch (ex) {}

            // get layout-container dimensions (updated when necessary)
            cDims = state.container = getElemDims( $Container ); // update data-pointer too
        };

        /**
	 * initHotkeys
	 *
	 * Bind layout hotkeys - if options enabled
	 *
	 * @callers  create()
	 */
        var initHotkeys = function () {
            // bind keyDown to capture hotkeys, if option enabled for ANY pane
            $.each(c.borderPanes.split(","), function (i,pane) {
                var o = options[pane];
                if (o.enableCursorHotkey || o.customHotkey) {
                    $(document).keydown( keyDown ); // only need to bind this ONCE
                    return false; // BREAK - binding was done
                }
            });
        };

        /**
	 * initOptions
	 *
	 * Build final CONFIG and OPTIONS data
	 *
	 * @callers  create()
	 */
        var initOptions = function () {
            // simplify logic by making sure passed 'opts' var has basic keys
            opts = transformData( opts );

            // update default effects, if case user passed key
            if (opts.effects) {
                $.extend( effects, opts.effects );
                delete opts.effects;
            }

            // see if any 'global options' were specified
            $.each("name,scrollToBookmarkOnLoad".split(","), function (idx,key) {
                if (opts[key] !== undefined)
                    options[key] = opts[key];
                else if (opts.defaults[key] !== undefined) {
                    options[key] = opts.defaults[key];
                    delete opts.defaults[key];
                }
            });

            // remove any 'defaults' that MUST be set 'per-pane'
            $.each("paneSelector,resizerCursor,customHotkey".split(","),
                function (idx,key) {
                    delete opts.defaults[key];
                } // is OK if key does not exist
                );

            // now update options.defaults
            $.extend( options.defaults, opts.defaults );
            // make sure required sub-keys exist
            //if (typeof options.defaults.fxSettings != "object") options.defaults.fxSettings = {};

            // merge all config & options for the 'center' pane
            c.center = $.extend( true, {}, c.defaults, c.center );
            $.extend( options.center, opts.center );
            // Most 'default options' do not apply to 'center', so add only those that DO
            var o_Center = $.extend( true, {}, options.defaults, opts.defaults, options.center ); // TEMP data
            $.each("paneClass,contentSelector,contentIgnoreSelector,applyDefaultStyles,showOverflowOnHover".split(","),
                function (idx,key) {
                    options.center[key] = o_Center[key];
                }
                );

            var defs = options.defaults;

            // create a COMPLETE set of options for EACH border-pane
            $.each(c.borderPanes.split(","), function(i,pane) {
                // apply 'pane-defaults' to CONFIG.PANE
                c[pane] = $.extend( true, {}, c.defaults, c[pane] );
                // apply 'pane-defaults' +  user-options to OPTIONS.PANE
                o = options[pane] = $.extend( true, {}, options.defaults, options[pane], opts.defaults, opts[pane] );

                // make sure we have base-classes
                if (!o.paneClass)		o.paneClass		= defaults.paneClass;
                if (!o.resizerClass)	o.resizerClass	= defaults.resizerClass;
                if (!o.togglerClass)	o.togglerClass	= defaults.togglerClass;

                // create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close]
                $.each(["_open","_close",""], function (i,n) { 
                    var
                    sName		= "fxName"+n
                    ,	sSpeed		= "fxSpeed"+n
                    ,	sSettings	= "fxSettings"+n
                    ;
                    // recalculate fxName according to specificity rules
                    o[sName] =
                    opts[pane][sName]		// opts.west.fxName_open
                    ||	opts[pane].fxName		// opts.west.fxName
                    ||	opts.defaults[sName]	// opts.defaults.fxName_open
                    ||	opts.defaults.fxName	// opts.defaults.fxName
                    ||	o[sName]				// options.west.fxName_open
                    ||	o.fxName				// options.west.fxName
                    ||	defs[sName]				// options.defaults.fxName_open
                    ||	defs.fxName				// options.defaults.fxName
                    ||	"none"
                    ;
                    // validate fxName to be sure is a valid effect
                    var fxName = o[sName];
                    if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings))
                        fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed
                    // set vars for effects subkeys to simplify logic
                    var
                    fx = effects[fxName]	|| {} // effects.slide
                    ,	fx_all	= fx.all		|| {} // effects.slide.all
                    ,	fx_pane	= fx[pane]		|| {} // effects.slide.west
                    ;
                    // RECREATE the fxSettings[_open|_close] keys using specificity rules
                    o[sSettings] = $.extend(
                    {}
                        ,	fx_all						// effects.slide.all
                        ,	fx_pane						// effects.slide.west
                        ,	defs.fxSettings || {}		// options.defaults.fxSettings
                        ,	defs[sSettings] || {}		// options.defaults.fxSettings_open
                        ,	o.fxSettings				// options.west.fxSettings
                        ,	o[sSettings]				// options.west.fxSettings_open
                        ,	opts.defaults.fxSettings	// opts.defaults.fxSettings
                        ,	opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open
                        ,	opts[pane].fxSettings		// opts.west.fxSettings
                        ,	opts[pane][sSettings] || {}	// opts.west.fxSettings_open
                        );
                    // recalculate fxSpeed according to specificity rules
                    o[sSpeed] =
                    opts[pane][sSpeed]		// opts.west.fxSpeed_open
                    ||	opts[pane].fxSpeed		// opts.west.fxSpeed (pane-default)
                    ||	opts.defaults[sSpeed]	// opts.defaults.fxSpeed_open
                    ||	opts.defaults.fxSpeed	// opts.defaults.fxSpeed
                    ||	o[sSpeed]				// options.west.fxSpeed_open
                    ||	o[sSettings].duration	// options.west.fxSettings_open.duration
                    ||	o.fxSpeed				// options.west.fxSpeed
                    ||	o.fxSettings.duration	// options.west.fxSettings.duration
                    ||	defs.fxSpeed			// options.defaults.fxSpeed
                    ||	defs.fxSettings.duration// options.defaults.fxSettings.duration
                    ||	fx_pane.duration		// effects.slide.west.duration
                    ||	fx_all.duration			// effects.slide.all.duration
                    ||	"normal"				// DEFAULT
                ;
                // DEBUG: if (pane=="east") debugData( $.extend({}, {speed: o[sSpeed], fxSettings_duration: o[sSettings].duration}, o[sSettings]), pane+"."+sName+" = "+fxName );
                });
            });
        };

        /**
	 * initPanes
	 *
	 * Initialize module objects, styling, size and position for all panes
	 *
	 * @callers  create()
	 */
        var initPanes = function () {
            // NOTE: do north & south FIRST so we can measure their height - do center LAST
            $.each(c.allPanes.split(","), function() {
                var 
                pane	= str(this)
                ,	o		= options[pane]
                ,	s		= state[pane]
                ,	fx		= s.fx
                ,	dir		= c[pane].dir
                //	if o.size is not > 0, then we will use MEASURE the pane and use that as it's 'size'
                ,	size	= o.size=="auto" || isNaN(o.size) ? 0 : o.size
                ,	minSize	= o.minSize || 1
                ,	maxSize	= o.maxSize || 9999
                ,	spacing	= o.spacing_open || 0
                ,	sel		= o.paneSelector
                ,	isIE6	= ($.browser.msie && $.browser.version < 7)
                ,	CSS		= {}
                ,	$P, $C
                ;
                $Cs[pane] = false; // init

                if (sel.substr(0,1)==="#") // ID selector
                    // NOTE: elements selected 'by ID' DO NOT have to be 'children'
                    $P = $Ps[pane] = $Container.find(sel+":first");
                else { // class or other selector
                    $P = $Ps[pane] = $Container.children(sel+":first");
                    // look for the pane nested inside a 'form' element
                    if (!$P.length) $P = $Ps[pane] = $Container.children("form:first").children(sel+":first");
                }

                if (!$P.length) {
                    $Ps[pane] = false; // logic
                    return true; // SKIP to next
                }

                // add basic classes & attributes
                $P
                .attr("pane", pane) // add pane-identifier
                .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
                ;

                // init pane-logic vars, etc.
                if (pane != "center") {
                    s.isClosed  = false; // true = pane is closed
                    s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
                    s.isResizing= false; // true = pane is in process of being resized
                    s.isHidden	= false; // true = pane is hidden - no spacing, resizer or toggler is visible!
                    s.noRoom	= false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically
                    // create special keys for internal use
                    c[pane].pins = [];   // used to track and sync 'pin-buttons' for border-panes
                }

                CSS = $.extend({
                    visibility: "visible", 
                    display: "block"
                }, c.defaults.cssReq, c[pane].cssReq );
                if (o.applyDefaultStyles) $.extend( CSS, c.defaults.cssDef, c[pane].cssDef ); // cosmetic defaults
                $P.css(CSS); // add base-css BEFORE 'measuring' to calc size & position
                CSS = {};	// reset var

                // set css-position to account for container borders & padding
                switch (pane) {
                    case "north":
                        CSS.top 	= cDims.top;
                        CSS.left 	= cDims.left;
                        CSS.right	= cDims.right;
                        break;
                    case "south":
                        CSS.bottom	= cDims.bottom;
                        CSS.left 	= cDims.left;
                        CSS.right 	= cDims.right;
                        break;
                    case "west":
                        CSS.left 	= cDims.left; // top, bottom & height set by sizeMidPanes()
                        break;
                    case "east":
                        CSS.right 	= cDims.right; // ditto
                        break;
                    case "center":	// top, left, width & height set by sizeMidPanes()
                }

                if (dir == "horz") { // north or south pane
                    if (size === 0 || size == "auto") {
                        $P.css({
                            height: "auto"
                        });
                        size = $P.outerHeight();
                    }
                    size = max(size, minSize);
                    size = min(size, maxSize);
                    size = min(size, cDims.innerHeight - spacing);
                    CSS.height = max(1, cssH(pane, size));
                    s.size = size; // update state
                    // make sure minSize is sufficient to avoid errors
                    s.maxSize = maxSize; // init value
                    s.minSize = max(minSize, size - CSS.height + 1); // = pane.outerHeight when css.height = 1px
                    // handle IE6
                    //if (isIE6) CSS.width = cssW($P, cDims.innerWidth);
                    $P.css(CSS); // apply size & position
                }
                else if (dir == "vert") { // east or west pane
                    if (size === 0 || size == "auto") {
                        $P.css({
                            width: "auto", 
                            float: "left"
                        }); // float = FORCE pane to auto-size
                        size = $P.outerWidth();
                        $P.css({
                            float: "none"
                        }); // RESET
                    }
                    size = max(size, minSize);
                    size = min(size, maxSize);
                    size = min(size, cDims.innerWidth - spacing);
                    CSS.width = max(1, cssW(pane, size));
                    s.size = size; // update state
                    s.maxSize = maxSize; // init value
                    // make sure minSize is sufficient to avoid errors
                    s.minSize = max(minSize, size - CSS.width + 1); // = pane.outerWidth when css.width = 1px
                    $P.css(CSS); // apply size - top, bottom & height set by sizeMidPanes
                    sizeMidPanes(pane, null, true); // true = onInit
                }
                else if (pane == "center") {
                    $P.css(CSS); // top, left, width & height set by sizeMidPanes...
                    sizeMidPanes("center", null, true); // true = onInit
                }

                // close or hide the pane if specified in settings
                if (o.initClosed && o.closable) {
                    $P.hide().addClass("closed");
                    s.isClosed = true;
                }
                else if (o.initHidden || o.initClosed) {
                    hide(pane, true); // will be completely invisible - no resizer or spacing
                    s.isHidden = true;
                }
                else
                    $P.addClass("open");

                // check option for auto-handling of pop-ups & drop-downs
                if (o.showOverflowOnHover)
                    $P.hover( allowOverflow, resetOverflow );

                /*
			 *	see if this pane has a 'content element' that we need to auto-size
			 */
                if (o.contentSelector) {
                    $C = $Cs[pane] = $P.children(o.contentSelector+":first"); // match 1-element only
                    if (!$C.length) {
                        $Cs[pane] = false;
                        return true; // SKIP to next
                    }
                    $C.css( c.content.cssReq );
                    if (o.applyDefaultStyles) $C.css( c.content.cssDef ); // cosmetic defaults
                    // NO PANE-SCROLLING when there is a content-div
                    $P.css({
                        overflow: "hidden"
                    });
                }
            });
        };

        /**
	 * initHandles
	 *
	 * Initialize module objects, styling, size and position for all resize bars and toggler buttons
	 *
	 * @callers  create()
	 */
        var initHandles = function () {
            // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
            $.each(c.borderPanes.split(","), function() {
                var 
                pane	= str(this)
                ,	o		= options[pane]
                ,	s		= state[pane]
                ,	rClass	= o.resizerClass
                ,	tClass	= o.togglerClass
                ,	$P		= $Ps[pane]
                ;
                $Rs[pane] = false; // INIT
                $Ts[pane] = false;

                if (!$P || (!o.closable && !o.resizable)) return; // pane does not exist - skip

                var 
                edge	= c[pane].edge
                ,	isOpen	= $P.is(":visible")
                ,	spacing	= (isOpen ? o.spacing_open : o.spacing_closed)
                ,	_pane	= "-"+ pane // used for classNames
                ,	_state	= (isOpen ? "-open" : "-closed") // used for classNames
                ,	$R, $T
                ;
                // INIT RESIZER BAR
                $R = $Rs[pane] = $("<span></span>");
	
                if (isOpen && o.resizable)
                    ; // this is handled by initResizable
                else if (!isOpen && o.slidable)
                    $R.attr("title", o.sliderTip).css("cursor", o.sliderCursor);
	
                $R
                // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
                .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : ""))
                .attr("resizer", pane) // so we can read this from the resizer
                .css(c.resizers.cssReq) // add base/required styles
                // POSITION of resizer bar - allow for container border & padding
                .css(edge, cDims[edge] + getPaneSize(pane))
                // ADD CLASSNAMES - eg: class="resizer resizer-west resizer-open"
                .addClass( rClass +" "+ rClass+_pane +" "+ rClass+_state +" "+ rClass+_pane+_state )
                .appendTo($Container) // append DIV to container
                ;
                // ADD VISUAL STYLES
                if (o.applyDefaultStyles)
                    $R.css(c.resizers.cssDef);

                if (o.closable) {
                    // INIT COLLAPSER BUTTON
                    $T = $Ts[pane] = $("<div></div>");
                    $T
                    // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-toggler"
                    .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : ""))
                    .css(c.togglers.cssReq) // add base/required styles
                    .attr("title", (isOpen ? o.togglerTip_open : o.togglerTip_closed))
                    .click(function(evt){
                        toggle(pane);
                        evt.stopPropagation();
                    })
                    .mouseover(function(evt){
                        evt.stopPropagation();
                    }) // prevent resizer event
                    // ADD CLASSNAMES - eg: class="toggler toggler-west toggler-west-open"
                    .addClass( tClass +" "+ tClass+_pane +" "+ tClass+_state +" "+ tClass+_pane+_state )
                    .appendTo($R) // append SPAN to resizer DIV
                    ;

                    // ADD INNER-SPANS TO TOGGLER
                    if (o.togglerContent_open) // ui-layout-open
                        $("<span>"+ o.togglerContent_open +"</span>")
                        .addClass("content content-open")
                        .css("display", s.isClosed ? "none" : "block")
                        .appendTo( $T )
                    ;
                    if (o.togglerContent_closed) // ui-layout-closed
                        $("<span>"+ o.togglerContent_closed +"</span>")
                        .addClass("content content-closed")
                        .css("display", s.isClosed ? "block" : "none")
                        .appendTo( $T )
                    ;

                    // ADD BASIC VISUAL STYLES
                    if (o.applyDefaultStyles)
                        $T.css(c.togglers.cssDef);

                    if (!isOpen) bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true
                }

            });

            // SET ALL HANDLE SIZES & LENGTHS
            sizeHandles("all", true); // true = onInit
        };

        /**
	 * initResizable
	 *
	 * Add resize-bars to all panes that specify it in options
	 *
	 * @dependancies  $.fn.resizable - will abort if not found
	 * @callers  create()
	 */
        var initResizable = function () {
            var
            draggingAvailable = (typeof $.fn.draggable == "function")
            ,	minPosition, maxPosition, edge // set in start()
            ;

            $.each(c.borderPanes.split(","), function() {
                var 
                pane	= str(this)
                ,	o		= options[pane]
                ,	s		= state[pane]
                ;
                if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
                    o.resizable = false;
                    return true; // skip to next
                }

                var 
                rClass				= o.resizerClass
                //	'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
                ,	dragClass			= rClass+"-drag"			// resizer-drag
                ,	dragPaneClass		= rClass+"-"+pane+"-drag"	// resizer-north-drag
                //	'dragging' class is applied to the CLONED resizer-bar while it is being dragged
                ,	draggingClass		= rClass+"-dragging"		// resizer-dragging
                ,	draggingPaneClass	= rClass+"-"+pane+"-dragging" // resizer-north-dragging
                ,	draggingClassSet	= false 					// logic var
                ,	$P 					= $Ps[pane]
                ,	$R					= $Rs[pane]
                ;

                if (!s.isClosed)
                    $R
                    .attr("title", o.resizerTip)
                    .css("cursor", o.resizerCursor) // n-resize, s-resize, etc
                ;

                $R.draggable({
                    containment:	$Container[0] // limit resizing to layout container
                    ,	
                    axis:			(c[pane].dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis
                    ,	
                    delay:			200
                    ,	
                    distance:		1
                    //	basic format for helper - style it using class: .ui-draggable-dragging
                    ,	
                    helper:			"clone"
                    ,	
                    opacity:		o.resizerDragOpacity
                    //,	iframeFix:		o.draggableIframeFix // TODO: consider using when bug is fixed
                    ,	
                    zIndex:			c.zIndex.resizing

                    ,	
                    start: function (e, ui) {
                        // onresize_start callback - will CANCEL hide if returns false
                        // TODO: CONFIRM that dragging can be cancelled like this???
                        if (false === execUserCallback(pane, o.onresize_start)) return false;

                        s.isResizing = true; // prevent pane from closing while resizing
                        clearTimer(pane, "closeSlider"); // just in case already triggered

                        $R.addClass( dragClass +" "+ dragPaneClass ); // add drag classes
                        draggingClassSet = false; // reset logic var - see drag()

                        // SET RESIZING LIMITS - used in drag()
                        var resizerWidth = (pane=="east" || pane=="south" ? o.spacing_open : 0);
                        setPaneMinMaxSizes(pane); // update pane-state
                        s.minPosition -= resizerWidth;
                        s.maxPosition -= resizerWidth;
                        edge = (c[pane].dir=="horz" ? "top" : "left");

                        // MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS
                        $(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).each(function() {					
                            $('<div class="ui-layout-mask"/>')
                            .css({
                                background:	"#fff"
                                ,	
                                opacity:	"0.001"
                                ,	
                                zIndex:		9
                                ,	
                                position:	"absolute"
                                ,	
                                width:		this.offsetWidth+"px"
                                ,	
                                height:		this.offsetHeight+"px"
                            })
                            .css($(this).offset()) // top & left
                            .appendTo(this.parentNode) // put div INSIDE pane to avoid zIndex issues
                        ;
                        });
                    }

                    ,	
                    drag: function (e, ui) {
                        if (!draggingClassSet) { // can only add classes after clone has been added to the DOM
                            $(".ui-draggable-dragging")
                            .addClass( draggingClass +" "+ draggingPaneClass ) // add dragging classes
                            .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar
                            ;
                            draggingClassSet = true;
                            // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
                            if (s.isSliding) $Ps[pane].css("zIndex", c.zIndex.sliding);
                        }
                        // CONTAIN RESIZER-BAR TO RESIZING LIMITS
                        if		(ui.position[edge] < s.minPosition) ui.position[edge] = s.minPosition;
                        else if (ui.position[edge] > s.maxPosition) ui.position[edge] = s.maxPosition;
                    }

                    ,	
                    stop: function (e, ui) {
                        var 
                        dragPos	= ui.position
                        ,	resizerPos
                        ,	newSize
                        ;
                        $R.removeClass( dragClass +" "+ dragPaneClass ); // remove drag classes
	
                        switch (pane) {
                            case "north":
                                resizerPos = dragPos.top;
                                break;
                            case "west":
                                resizerPos = dragPos.left;
                                break;
                            case "south":
                                resizerPos = cDims.outerHeight - dragPos.top - $R.outerHeight();
                                break;
                            case "east":
                                resizerPos = cDims.outerWidth - dragPos.left - $R.outerWidth();
                                break;
                        }
                        // remove container margin from resizer position to get the pane size
                        newSize = resizerPos - cDims[ c[pane].edge ];

                        sizePane(pane, newSize);

                        // UN-MASK PANES MASKED IN drag.start
                        $("div.ui-layout-mask").remove(); // Remove iframe masks	

                        s.isResizing = false;
                    }

                });
            });
        };



        /*
 * ###########################
 *       ACTION METHODS
 * ###########################
 */

        /**
	 * hide / show
	 *
	 * Completely 'hides' a pane, including its spacing - as if it does not exist
	 * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
	 *
	 * @param String  pane   The pane being hidden, ie: north, south, east, or west
	 */
        var hide = function (pane, onInit) {
            var
            o	= options[pane]
            ,	s	= state[pane]
            ,	$P	= $Ps[pane]
            ,	$R	= $Rs[pane]
            ;
            if (!$P || s.isHidden) return; // pane does not exist OR is already hidden

            // onhide_start callback - will CANCEL hide if returns false
            if (false === execUserCallback(pane, o.onhide_start)) return;

            s.isSliding = false; // just in case

            // now hide the elements
            if ($R) $R.hide(); // hide resizer-bar
            if (onInit || s.isClosed) {
                s.isClosed = true; // to trigger open-animation on show()
                s.isHidden  = true;
                $P.hide(); // no animation when loading page
                sizeMidPanes(c[pane].dir == "horz" ? "all" : "center");
                execUserCallback(pane, o.onhide_end || o.onhide);
            }
            else {
                s.isHiding = true; // used by onclose
                close(pane, false); // adjust all panes to fit
            //s.isHidden  = true; - will be set by close - if not cancelled
            }
        };

        var show = function (pane, openPane) {
            var
            o	= options[pane]
            ,	s	= state[pane]
            ,	$P	= $Ps[pane]
            ,	$R	= $Rs[pane]
            ;
            if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden

            // onhide_start callback - will CANCEL hide if returns false
            if (false === execUserCallback(pane, o.onshow_start)) return;

            s.isSliding = false; // just in case
            s.isShowing = true; // used by onopen/onclose
            //s.isHidden  = false; - will be set by open/close - if not cancelled

            // now show the elements
            if ($R && o.spacing_open > 0) $R.show();
            if (openPane === false)
                close(pane, true); // true = force
            else
                open(pane); // adjust all panes to fit
        };


        /**
	 * toggle
	 *
	 * Toggles a pane open/closed by calling either open or close
	 *
	 * @param String  pane   The pane being toggled, ie: north, south, east, or west
	 */
        var toggle = function (pane) {
            var s = state[pane];
            if (s.isHidden)
                show(pane); // will call 'open' after unhiding it
            else if (s.isClosed)
                open(pane);
            else
                close(pane);
        };

        /**
	 * close
	 *
	 * Close the specified pane (animation optional), and resize all other panes as needed
	 *
	 * @param String  pane   The pane being closed, ie: north, south, east, or west
	 */
        var close = function (pane, force, noAnimation) {
            var 
            $P		= $Ps[pane]
            ,	$R		= $Rs[pane]
            ,	$T		= $Ts[pane]
            ,	o		= options[pane]
            ,	s		= state[pane]
            ,	doFX	= !noAnimation && !s.isClosed && (o.fxName_close != "none")
            ,	edge	= c[pane].edge
            ,	rClass	= o.resizerClass
            ,	tClass	= o.togglerClass
            ,	_pane	= "-"+ pane // used for classNames
            ,	_open	= "-open"
            ,	_sliding= "-sliding"
            ,	_closed	= "-closed"
            // 	transfer logic vars to temp vars
            ,	isShowing = s.isShowing
            ,	isHiding = s.isHiding
            ;
            // now clear the logic vars
            delete s.isShowing;
            delete s.isHiding;

            if (!$P || (!o.resizable && !o.closable)) return; // invalid request
            else if (!force && s.isClosed && !isShowing) return; // already closed

            if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation
                setFlowCallback("close", pane, force); // set a callback for this action, if possible
                return; // ABORT 
            }

            // onclose_start callback - will CANCEL hide if returns false
            // SKIP if just 'showing' a hidden pane as 'closed'
            if (!isShowing && false === execUserCallback(pane, o.onclose_start)) return;

            // SET flow-control flags
            c[pane].isMoving = true;
            c.isLayoutBusy = true;

            s.isClosed = true;
            // update isHidden BEFORE sizing panes
            if (isHiding) s.isHidden = true;
            else if (isShowing) s.isHidden = false;

            // sync any 'pin buttons'
            syncPinBtns(pane, false);

            // resize panes adjacent to this one
            if (!s.isSliding) sizeMidPanes(c[pane].dir == "horz" ? "all" : "center");

            // if this pane has a resizer bar, move it now
            if ($R) {
                $R
                .css(edge, cDims[edge]) // move the resizer bar
                .removeClass( rClass+_open +" "+ rClass+_pane+_open )
                .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
                .addClass( rClass+_closed +" "+ rClass+_pane+_closed )
                ;
                // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent
                if (o.resizable)
                    $R
                    .draggable("disable")
                    .css("cursor", "default")
                    .attr("title","")
                ;
                // if pane has a toggler button, adjust that too
                if ($T) {
                    $T
                    .removeClass( tClass+_open +" "+ tClass+_pane+_open )
                    .addClass( tClass+_closed +" "+ tClass+_pane+_closed )
                    .attr("title", o.togglerTip_closed) // may be blank
                ;
                }
                sizeHandles(); // resize 'length' and position togglers for adjacent panes
            }

            // ANIMATE 'CLOSE' - if no animation, then was ALREADY shown above
            if (doFX) {
                lockPaneForFX(pane, true); // need to set left/top so animation will work
                $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () {
                    lockPaneForFX(pane, false); // undo
                    if (!s.isClosed) return; // pane was opened before animation finished!
                    close_2();
                });
            }
            else {
                $P.hide(); // just hide pane NOW
                close_2();
            }

            // SUBROUTINE
            function close_2 () {
                bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true

                // onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
                if (!isShowing)	execUserCallback(pane, o.onclose_end || o.onclose);
                // onhide OR onshow callback
                if (isShowing)	execUserCallback(pane, o.onshow_end || o.onshow);
                if (isHiding)	execUserCallback(pane, o.onhide_end || o.onhide);

                // internal flow-control callback
                execFlowCallback(pane);
            }
        };

        /**
	 * open
	 *
	 * Open the specified pane (animation optional), and resize all other panes as needed
	 *
	 * @param String  pane   The pane being opened, ie: north, south, east, or west
	 */
        var open = function (pane, slide, noAnimation) {
            var 
            $P		= $Ps[pane]
            ,	$R		= $Rs[pane]
            ,	$T		= $Ts[pane]
            ,	o		= options[pane]
            ,	s		= state[pane]
            ,	doFX	= !noAnimation && s.isClosed && (o.fxName_open != "none")
            ,	edge	= c[pane].edge
            ,	rClass	= o.resizerClass
            ,	tClass	= o.togglerClass
            ,	_pane	= "-"+ pane // used for classNames
            ,	_open	= "-open"
            ,	_closed	= "-closed"
            ,	_sliding= "-sliding"
            // 	transfer logic var to temp var
            ,	isShowing = s.isShowing
            ;
            // now clear the logic var
            delete s.isShowing;

            if (!$P || (!o.resizable && !o.closable)) return; // invalid request
            else if (!s.isClosed && !s.isSliding) return; // already open

            // pane can ALSO be unhidden by just calling show(), so handle this scenario
            if (s.isHidden && !isShowing) {
                show(pane, true);
                return;
            }

            if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation
                setFlowCallback("open", pane, slide); // set a callback for this action, if possible
                return; // ABORT
            }

            // onopen_start callback - will CANCEL hide if returns false
            if (false === execUserCallback(pane, o.onopen_start)) return;

            // SET flow-control flags
            c[pane].isMoving = true;
            c.isLayoutBusy = true;

            // 'PIN PANE' - stop sliding
            if (s.isSliding && !slide) // !slide = 'open pane normally' - NOT sliding
                bindStopSlidingEvents(pane, false); // will set isSliding=false

            s.isClosed = false;
            // update isHidden BEFORE sizing panes
            if (isShowing) s.isHidden = false;

            // Container size may have changed - shrink the pane if now 'too big'
            setPaneMinMaxSizes(pane); // update pane-state
            if (s.size > s.maxSize) // pane is too big! resize it before opening
                $P.css( c[pane].sizeType, max(1, cssSize(pane, s.maxSize)) );

            bindStartSlidingEvent(pane, false); // remove trigger event from resizer-bar

            if (doFX) { // ANIMATE
                lockPaneForFX(pane, true); // need to set left/top so animation will work
                $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
                    lockPaneForFX(pane, false); // undo
                    if (s.isClosed) return; // pane was closed before animation finished!
                    open_2(); // continue
                });
            }
            else {// no animation
                $P.show();	// just show pane and...
                open_2();	// continue
            }

            // SUBROUTINE
            function open_2 () {
                // NOTE: if isSliding, then other panes are NOT 'resized'
                if (!s.isSliding) // resize all panes adjacent to this one
                    sizeMidPanes(c[pane].dir=="vert" ? "center" : "all");

                // if this pane has a toggler, move it now
                if ($R) {
                    $R
                    .css(edge, cDims[edge] + getPaneSize(pane)) // move the toggler
                    .removeClass( rClass+_closed +" "+ rClass+_pane+_closed )
                    .addClass( rClass+_open +" "+ rClass+_pane+_open )
                    .addClass( !s.isSliding ? "" : rClass+_sliding +" "+ rClass+_pane+_sliding )
                    ;
                    if (o.resizable)
                        $R
                        .draggable("enable")
                        .css("cursor", o.resizerCursor)
                        .attr("title", o.resizerTip)
                    ;
                    else
                        $R.css("cursor", "default"); // n-resize, s-resize, etc
                    // if pane also has a toggler button, adjust that too
                    if ($T) {
                        $T
                        .removeClass( tClass+_closed +" "+ tClass+_pane+_closed )
                        .addClass( tClass+_open +" "+ tClass+_pane+_open )
                        .attr("title", o.togglerTip_open) // may be blank
                    ;
                    }
                    sizeHandles("all"); // resize resizer & toggler sizes for all panes
                }

                // resize content every time pane opens - to be sure
                sizeContent(pane);

                // sync any 'pin buttons'
                syncPinBtns(pane, !s.isSliding);

                // onopen callback
                execUserCallback(pane, o.onopen_end || o.onopen);

                // onshow callback
                if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow);

                // internal flow-control callback
                execFlowCallback(pane);
            }
        };
	

        /**
	 * lockPaneForFX
	 *
	 * Must set left/top on East/South panes so animation will work properly
	 *
	 * @param String  pane  The pane to lock, 'east' or 'south' - any other is ignored!
	 * @param Boolean  doLock  true = set left/top, false = remove
	 */
        var lockPaneForFX = function (pane, doLock) {
            var $P = $Ps[pane];
            if (doLock) {
                $P.css({
                    zIndex: c.zIndex.animation
                }); // overlay all elements during animation
                if (pane=="south")
                    $P.css({
                        top: cDims.top + cDims.innerHeight - $P.outerHeight()
                    });
                else if (pane=="east")
                    $P.css({
                        left: cDims.left + cDims.innerWidth - $P.outerWidth()
                    });
            }
            else {
                if (!state[pane].isSliding) $P.css({
                    zIndex: c.zIndex.pane_normal
                });
                if (pane=="south")
                    $P.css({
                        top: "auto"
                    });
                else if (pane=="east")
                    $P.css({
                        left: "auto"
                    });
            }
        };


        /**
	 * bindStartSlidingEvent
	 *
	 * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
	 *
	 * @callers  open(), close()
	 * @param String  pane  The pane to enable/disable, 'north', 'south', etc.
	 * @param Boolean  enable  Enable or Disable sliding?
	 */
        var bindStartSlidingEvent = function (pane, enable) {
            var 
            o		= options[pane]
            ,	$R		= $Rs[pane]
            ,	trigger	= o.slideTrigger_open
            ;
            if (!$R || !o.slidable) return;
            // make sure we have a valid event
            if (trigger != "click" && trigger != "dblclick" && trigger != "mouseover") trigger = "click";
            $R
            // add or remove trigger event
            [enable ? "bind" : "unbind"](trigger, slideOpen)
            // set the appropriate cursor & title/tip
            .css("cursor", (enable ? o.sliderCursor: "default"))
            .attr("title", (enable ? o.sliderTip : ""))
        ;
        };

        /**
	 * bindStopSlidingEvents
	 *
	 * Add or remove 'mouseout' events to 'slide close' when pane is 'sliding' open or closed
	 * Also increases zIndex when pane is sliding open
	 * See bindStartSlidingEvent for code to control 'slide open'
	 *
	 * @callers  slideOpen(), slideClosed()
	 * @param String  pane  The pane to process, 'north', 'south', etc.
	 * @param Boolean  isOpen  Is pane open or closed?
	 */
        var bindStopSlidingEvents = function (pane, enable) {
            var 
            o		= options[pane]
            ,	s		= state[pane]
            ,	trigger	= o.slideTrigger_close
            ,	action	= (enable ? "bind" : "unbind") // can't make 'unbind' work! - see disabled code below
            ,	$P		= $Ps[pane]
            ,	$R		= $Rs[pane]
            ;

            s.isSliding = enable; // logic
            clearTimer(pane, "closeSlider"); // just in case

            // raise z-index when sliding
            $P.css({
                zIndex: (enable ? c.zIndex.sliding : c.zIndex.pane_normal)
            });
            $R.css({
                zIndex: (enable ? c.zIndex.sliding : c.zIndex.resizer_normal)
            });

            // make sure we have a valid event
            if (trigger != "click" && trigger != "mouseout") trigger = "mouseout";

            // when trigger is 'mouseout', must cancel timer when mouse moves between 'pane' and 'resizer'
            if (enable) { // BIND trigger events
                $P.bind(trigger, slideClosed );
                $R.bind(trigger, slideClosed );
                if (trigger = "mouseout") {
                    $P.bind("mouseover", cancelMouseOut );
                    $R.bind("mouseover", cancelMouseOut );
                }
            }
            else { // UNBIND trigger events
                // TODO: why does unbind of a 'single function' not work reliably?
                //$P[action](trigger, slideClosed );
                $P.unbind(trigger);
                $R.unbind(trigger);
                if (trigger = "mouseout") {
                    //$P[action]("mouseover", cancelMouseOut );
                    $P.unbind("mouseover");
                    $R.unbind("mouseover");
                    clearTimer(pane, "closeSlider");
                }
            }

            // SUBROUTINE for mouseout timer clearing
            function cancelMouseOut (evt) {
                clearTimer(pane, "closeSlider");
                evt.stopPropagation();
            }
        };

        var slideOpen = function () {
            var pane = $(this).attr("resizer"); // attr added by initHandles
            if (state[pane].isClosed) { // skip if already open!
                bindStopSlidingEvents(pane, true); // pane is opening, so BIND trigger events to close it
                open(pane, true); // true = slide - ie, called from here!
            }
        };

        var slideClosed = function () {
            var
            $E = $(this)
            ,	pane = $E.attr("pane") || $E.attr("resizer")
            ,	o = options[pane]
            ,	s = state[pane]
            ;
            if (s.isClosed || s.isResizing)
                return; // skip if already closed OR in process of resizing
            else if (o.slideTrigger_close == "click")
                close_NOW(); // close immediately onClick
            else // trigger = mouseout - use a delay
                setTimer(pane, "closeSlider", close_NOW, 300); // .3 sec delay

            // SUBROUTINE for timed close
            function close_NOW () {
                bindStopSlidingEvents(pane, false); // pane is being closed, so UNBIND trigger events
                if (!s.isClosed) close(pane); // skip if already closed!
            }
        };


        /**
	 * sizePane
	 *
	 * @callers  initResizable.stop()
	 * @param String  pane   The pane being resized - usually west or east, but potentially north or south
	 * @param Integer  newSize  The new size for this pane - will be validated
	 */
        var sizePane = function (pane, size) {
            // TODO: accept "auto" as size, and size-to-fit pane content
            var 
            edge	= c[pane].edge
            ,	dir		= c[pane].dir
            ,	o		= options[pane]
            ,	s		= state[pane]
            ,	$P		= $Ps[pane]
            ,	$R		= $Rs[pane]
            ;
            // calculate 'current' min/max sizes
            setPaneMinMaxSizes(pane); // update pane-state
            // compare/update calculated min/max to user-options
            s.minSize = max(s.minSize, o.minSize);
            if (o.maxSize > 0) s.maxSize = min(s.maxSize, o.maxSize);
            // validate passed size
            size = max(size, s.minSize);
            size = min(size, s.maxSize);
            s.size = size; // update state

            // move the resizer bar and resize the pane
            $R.css( edge, size + cDims[edge] );
            $P.css( c[pane].sizeType, max(1, cssSize(pane, size)) );

            // resize all the adjacent panes, and adjust their toggler buttons
            if (!s.isSliding) sizeMidPanes(dir=="horz" ? "all" : "center");
            sizeHandles();
            sizeContent(pane);
            execUserCallback(pane, o.onresize_end || o.onresize);
        };

        /**
	 * sizeMidPanes
	 *
	 * @callers  create(), open(), close(), onWindowResize()
	 */
        var sizeMidPanes = function (panes, overrideDims, onInit) {
            if (!panes || panes == "all") panes = "east,west,center";

            var d = getPaneDims();
            if (overrideDims) $.extend( d, overrideDims );

            $.each(panes.split(","), function() {
                if (!$Ps[this]) return; // NO PANE - skip
                var 
                pane	= str(this)
                ,	o		= options[pane]
                ,	s		= state[pane]
                ,	$P		= $Ps[pane]
                ,	$R		= $Rs[pane]
                ,	hasRoom	= true
                ,	CSS		= {}
                ;

                if (pane == "center") {
                    d = getPaneDims(); // REFRESH Dims because may have just 'unhidden' East or West pane after a 'resize'
                    CSS = $.extend( {}, d ); // COPY ALL of the paneDims
                    CSS.width  = max(1, cssW(pane, CSS.width));
                    CSS.height = max(1, cssH(pane, CSS.height));
                    hasRoom = (CSS.width > 1 && CSS.height > 1);
                    /*
				 * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
				 * Normally these panes have only 'left' & 'right' positions so pane auto-sizes
				 */
                    if ($.browser.msie && (!$.boxModel || $.browser.version < 7)) {
                        if ($Ps.north) $Ps.north.css({
                            width: cssW($Ps.north, cDims.innerWidth)
                        });
                        if ($Ps.south) $Ps.south.css({
                            width: cssW($Ps.south, cDims.innerWidth)
                        });
                    }
                }
                else { // for east and west, set only the height
                    CSS.top = d.top;
                    CSS.bottom = d.bottom;
                    CSS.height = max(1, cssH(pane, d.height));
                    hasRoom = (CSS.height > 1);
                }

                if (hasRoom) {
                    $P.css(CSS);
                    if (s.noRoom) {
                        s.noRoom = false;
                        if (s.isHidden) return;
                        else show(pane, !s.isClosed);
                    /* OLD CODE - keep until sure line above works right!
					if (!s.isClosed) $P.show(); // in case was previously hidden due to NOT hasRoom
					if ($R) $R.show();
					*/
                    }
                    if (!onInit) {
                        sizeContent(pane);
                        execUserCallback(pane, o.onresize_end || o.onresize);
                    }
                }
                else if (!s.noRoom) { // no room for pane, so just hide it (if not already)
                    s.noRoom = true; // update state
                    if (s.isHidden) return;
                    if (onInit) { // skip onhide callback and other logic onLoad
                        $P.hide();
                        if ($R) $R.hide();
                    }
                    else hide(pane);
                }
            });
        };


        var sizeContent = function (panes) {
            if (!panes || panes == "all") panes = c.allPanes;

            $.each(panes.split(","), function() {
                if (!$Cs[this]) return; // NO CONTENT - skip
                var 
                pane	= str(this)
                ,	ignore	= options[pane].contentIgnoreSelector
                ,	$P		= $Ps[pane]
                ,	$C		= $Cs[pane]
                ,	e_C		= $C[0]		// DOM element
                ,	height	= cssH($P);	// init to pane.innerHeight
                ;
                $P.children().each(function() {
                    if (this == e_C) return; // Content elem - skip
                    var $E = $(this);
                    if (!ignore || !$E.is(ignore))
                        height -= $E.outerHeight();
                });
                if (height > 0)
                    height = cssH($C, height);
                if (height < 1)
                    $C.hide(); // no room for content!
                else
                    $C.css({
                        height: height
                    }).show();
            });
        };


        /**
	 * sizeHandles
	 *
	 * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
	 *
	 * @callers  initHandles(), open(), close(), resizeAll()
	 */
        var sizeHandles = function (panes, onInit) {
            if (!panes || panes == "all") panes = c.borderPanes;

            $.each(panes.split(","), function() {
                var 
                pane	= str(this)
                ,	o		= options[pane]
                ,	s		= state[pane]
                ,	$P		= $Ps[pane]
                ,	$R		= $Rs[pane]
                ,	$T		= $Ts[pane]
                ;
                if (!$P || !$R || (!o.resizable && !o.closable)) return; // skip

                var 
                dir			= c[pane].dir
                ,	_state		= (s.isClosed ? "_closed" : "_open")
                ,	spacing		= o["spacing"+ _state]
                ,	togAlign	= o["togglerAlign"+ _state]
                ,	togLen		= o["togglerLength"+ _state]
                ,	paneLen
                ,	offset
                ,	CSS = {}
                ;
                if (spacing == 0) {
                    $R.hide();
                    return;
                }
                else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
                    $R.show(); // in case was previously hidden

                // Resizer Bar is ALWAYS same width/height of pane it is attached to
                if (dir == "horz") { // north/south
                    paneLen = $P.outerWidth();
                    $R.css({
                        width:	max(1, cssW($R, paneLen)) // account for borders & padding
                        ,	
                        height:	max(1, cssH($R, spacing)) // ditto
                        ,	
                        left:	cssNum($P, "left")
                    });
                }
                else { // east/west
                    paneLen = $P.outerHeight();
                    $R.css({
                        height:	max(1, cssH($R, paneLen)) // account for borders & padding
                        ,	
                        width:	max(1, cssW($R, spacing)) // ditto
                        ,	
                        top:	cDims.top + getPaneSize("north", true)
                    //,	top:	cssNum($Ps["center"], "top")
                    });
				
                }

                if ($T) {
                    if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) {
                        $T.hide(); // always HIDE the toggler when 'sliding'
                        return;
                    }
                    else
                        $T.show(); // in case was previously hidden

                    if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) {
                        togLen = paneLen;
                        offset = 0;
                    }
                    else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
                        if (typeof togAlign == "string") {
                            switch (togAlign) {
                                case "top":
                                case "left":
                                    offset = 0;
                                    break;
                                case "bottom":
                                case "right":
                                    offset = paneLen - togLen;
                                    break;
                                case "middle":
                                case "center":
                                default:
                                    offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos
                            }
                        }
                        else { // togAlign = number
                            var x = parseInt(togAlign); //
                            if (togAlign >= 0) offset = x;
                            else offset = paneLen - togLen + x; // NOTE: x is negative!
                        }
                    }

                    var
                    $TC_o = (o.togglerContent_open   ? $T.children(".content-open") : false)
                    ,	$TC_c = (o.togglerContent_closed ? $T.children(".content-closed")   : false)
                    ,	$TC   = (s.isClosed ? $TC_c : $TC_o)
                    ;
                    if ($TC_o) $TC_o.css("display", s.isClosed ? "none" : "block");
                    if ($TC_c) $TC_c.css("display", s.isClosed ? "block" : "none");

                    if (dir == "horz") { // north/south
                        var width = cssW($T, togLen);
                        $T.css({
                            width:	max(0, width)  // account for borders & padding
                            ,	
                            height:	max(1, cssH($T, spacing)) // ditto
                            ,	
                            left:	offset // TODO: VERIFY that toggler  positions correctly for ALL values
                        });
                        if ($TC) // CENTER the toggler content SPAN
                            $TC.css("marginLeft", Math.floor((width-$TC.outerWidth())/2)); // could be negative
                    }
                    else { // east/west
                        var height = cssH($T, togLen);
                        $T.css({
                            height:	max(0, height)  // account for borders & padding
                            ,	
                            width:	max(1, cssW($T, spacing)) // ditto
                            ,	
                            top:	offset // POSITION the toggler
                        });
                        if ($TC) // CENTER the toggler content SPAN
                            $TC.css("marginTop", Math.floor((height-$TC.outerHeight())/2)); // could be negative
                    }


                }

                // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
                if (onInit && o.initHidden) {
                    $R.hide();
                    if ($T) $T.hide();
                }
            });
        };


        /**
	 * resizeAll
	 *
	 * @callers  window.onresize(), callbacks or custom code
	 */
        var resizeAll = function () {
            var
            oldW	= cDims.innerWidth
            ,	oldH	= cDims.innerHeight
            ;
            cDims = state.container = getElemDims($Container); // UPDATE container dimensions

            var
            checkH	= (cDims.innerHeight < oldH)
            ,	checkW	= (cDims.innerWidth < oldW)
            ,	s, dir
            ;

            if (checkH || checkW)
                // NOTE special order for sizing: S-N-E-W
                $.each(["south","north","east","west"], function(i,pane) {
                    s = state[pane];
                    dir = c[pane].dir;
                    if (!s.isClosed && ((checkH && dir=="horz") || (checkW && dir=="vert"))) {
                        setPaneMinMaxSizes(pane); // update pane-state
                        // shrink pane if 'too big' to fit
                        if (s.size > s.maxSize)
                            sizePane(pane, s.maxSize);
                    }
                });

            sizeMidPanes("all");
            sizeHandles("all"); // reposition the toggler elements
        };


        /**
	 * keyDown
	 *
	 * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
	 *
	 * @callers  document.keydown()
	 */
        function keyDown (evt) {
            if (!evt) return true;
            var code = evt.keyCode;
            if (code < 33) return true; // ignore special keys: ENTER, TAB, etc

            var
            PANE = {
                38: "north" // Up Cursor
                ,	
                40: "south" // Down Cursor
                ,	
                37: "west"  // Left Cursor
                ,	
                39: "east"  // Right Cursor
            }
            ,	isCursorKey = (code >= 37 && code <= 40)
            ,	ALT = evt.altKey // no worky!
            ,	SHIFT = evt.shiftKey
            ,	CTRL = evt.ctrlKey
            ,	pane = false
            ,	s, o, k, m, el
            ;

            if (!CTRL && !SHIFT)
                return true; // no modifier key - abort
            else if (isCursorKey && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
                pane = PANE[code];
            else // check to see if this matches a custom-hotkey
                $.each(c.borderPanes.split(","), function(i,p) { // loop each pane to check its hotkey
                    o = options[p];
                    k = o.customHotkey;
                    m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
                    if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches
                        if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
                            pane = p;
                            return false; // BREAK
                        }
                    }
                });

            if (!pane) return true; // no hotkey - abort

            // validate pane
            o = options[pane]; // get pane options
            s = state[pane]; // get pane options
            if (!o.enableCursorHotkey || s.isHidden || !$Ps[pane]) return true;

            // see if user is in a 'form field' because may be 'selecting text'!
            el = evt.target || evt.srcElement;
            if (el && SHIFT && isCursorKey && (el.tagName=="TEXTAREA" || (el.tagName=="INPUT" && (code==37 || code==39))))
                return true; // allow text-selection

            // SYNTAX NOTES
            // use "returnValue=false" to abort keystroke but NOT abort function - can run another command afterwards
            // use "return false" to abort keystroke AND abort function
            toggle(pane);
            evt.stopPropagation();
            evt.returnValue = false; // CANCEL key
            return false;
        };


        /*
 * ###########################
 *     UTILITY METHODS
 *   called externally only
 * ###########################
 */

        function allowOverflow (elem) {
            if (this && this.tagName) elem = this; // BOUND to element
            var $P;
            if (typeof elem=="string")
                $P = $Ps[elem];
            else {
                if ($(elem).attr("pane")) $P = $(elem);
                else $P = $(elem).parents("div[pane]:first");
            }
            if (!$P.length) return; // INVALID

            var
            pane	= $P.attr("pane")
            ,	s		= state[pane]
            ;

            // if pane is already raised, then reset it before doing it again!
            // this would happen if allowOverflow is attached to BOTH the pane and an element 
            if (s.cssSaved)
                resetOverflow(pane); // reset previous CSS before continuing

            // if pane is raised by sliding or resizing, or it's closed, then abort
            if (s.isSliding || s.isResizing || s.isClosed) {
                s.cssSaved = false;
                return;
            }

            var
            newCSS	= {
                zIndex: (c.zIndex.pane_normal + 1)
            }
            ,	curCSS	= {}
            ,	of		= $P.css("overflow")
            ,	ofX		= $P.css("overflowX")
            ,	ofY		= $P.css("overflowY")
            ;
            // determine which, if any, overflow settings need to be changed
            if (of != "visible") {
                curCSS.overflow = of;
                newCSS.overflow = "visible";
            }
            if (ofX && ofX != "visible" && ofX != "auto") {
                curCSS.overflowX = ofX;
                newCSS.overflowX = "visible";
            }
            if (ofY && ofY != "visible" && ofY != "auto") {
                curCSS.overflowY = ofX;
                newCSS.overflowY = "visible";
            }

            // save the current overflow settings - even if blank!
            s.cssSaved = curCSS;

            // apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
            $P.css( newCSS );

            // make sure the zIndex of all other panes is normal
            $.each(c.allPanes.split(","), function(i, p) {
                if (p != pane) resetOverflow(p);
            });

        };

        function resetOverflow (elem) {
            if (this && this.tagName) elem = this; // BOUND to element
            var $P;
            if (typeof elem=="string")
                $P = $Ps[elem];
            else {
                if ($(elem).hasClass("ui-layout-pane")) $P = $(elem);
                else $P = $(elem).parents("div[pane]:first");
            }
            if (!$P.length) return; // INVALID

            var
            pane	= $P.attr("pane")
            ,	s		= state[pane]
            ,	CSS		= s.cssSaved || {}
            ;
            // reset the zIndex
            if (!s.isSliding && !s.isResizing)
                $P.css("zIndex", c.zIndex.pane_normal);

            // reset Overflow - if necessary
            $P.css( CSS );

            // clear var
            s.cssSaved = false;
        };


        /**
	* getBtn
	*
	* Helper function to validate params received by addButton utilities
	*
	* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .toggle-button"
	* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
	* @returns  If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise 'false'
	*/
        function getBtn(selector, pane, action) {
            var
            $E = $(selector)
            ,	err = "Error Adding Button \n\nInvalid "
            ;
            if (!$E.length) // element not found
                alert(err+"selector: "+ selector);
            else if (c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified
                alert(err+"pane: "+ pane);
            else { // VALID
                var btn = options[pane].buttonClass +"-"+ action;
                $E.addClass( btn +" "+ btn +"-"+ pane );
                return $E;
            }
            return false;  // INVALID
        };


        /**
	* addToggleBtn
	*
	* Add a custom Toggler button for a pane
	*
	* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .toggle-button"
	* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
	*/
        function addToggleBtn (selector, pane) {
            var $E = getBtn(selector, pane, "toggle");
            if ($E)
                $E
                .attr("title", state[pane].isClosed ? "Open" : "Close")
                .click(function (evt) {
                    toggle(pane);
                    evt.stopPropagation();
                })
        ;
        };

        /**
	* addOpenBtn
	*
	* Add a custom Open button for a pane
	*
	* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .open-button"
	* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
	*/
        function addOpenBtn (selector, pane) {
            var $E = getBtn(selector, pane, "open");
            if ($E)
                $E
                .attr("title", "Open")
                .click(function (evt) {
                    open(pane);
                    evt.stopPropagation();
                })
        ;
        };

        /**
	* addCloseBtn
	*
	* Add a custom Close button for a pane
	*
	* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .close-button"
	* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
	*/
        function addCloseBtn (selector, pane) {
            var $E = getBtn(selector, pane, "close");
            if ($E)
                $E
                .attr("title", "Close")
                .click(function (evt) {
                    close(pane);
                    evt.stopPropagation();
                })
        ;
        };

        /**
	* addPinBtn
	*
	* Add a custom Pin button for a pane
	*
	* Four classes are added to the element, based on the paneClass for the associated pane...
	* Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
	*  - ui-layout-pane-pin
	*  - ui-layout-pane-west-pin
	*  - ui-layout-pane-pin-up
	*  - ui-layout-pane-west-pin-up
	*
	* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .ui-layout-pin"
	* @param String   pane 		Name of the pane the pin is for: 'north', 'south', etc.
	*/
        function addPinBtn (selector, pane) {
            var $E = getBtn(selector, pane, "pin");
            if ($E) {
                var s = state[pane];
                $E.click(function (evt) {
                    setPinState($(this), pane, (s.isSliding || s.isClosed));
                    if (s.isSliding || s.isClosed) open( pane ); // change from sliding to open
                    else close( pane ); // slide-closed
                    evt.stopPropagation();
                });
                // add up/down pin attributes and classes
                setPinState ($E, pane, (!s.isClosed && !s.isSliding));
                // add this pin to the pane data so we can 'sync it' automatically
                // PANE.pins key is an array so we can store multiple pins for each pane
                c[pane].pins.push( selector ); // just save the selector string
            }
        };

        /**
	* syncPinBtns
	*
	* INTERNAL function to sync 'pin buttons' when pane is opened or closed
	* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
	*
	* @callers  open(), close()
	* @params  pane   These are the params returned to callbacks by layout()
	* @params  doPin  True means set the pin 'down', False means 'up'
	*/
        function syncPinBtns (pane, doPin) {
            $.each(c[pane].pins, function (i, selector) {
                setPinState($(selector), pane, doPin);
            });
        };

        /**
	* setPinState
	*
	* Change the class of the pin button to make it look 'up' or 'down'
	*
	* @callers  addPinBtn(), syncPinBtns()
	* @param Element  $Pin		The pin-span element in a jQuery wrapper
	* @param Boolean  doPin		True = set the pin 'down', False = set it 'up'
	* @param String   pinClass	The root classname for pins - will add '-up' or '-down' suffix
	*/
        function setPinState ($Pin, pane, doPin) {
            var updown = $Pin.attr("pin");
            if (updown && doPin == (updown=="down")) return; // already in correct state
            var
            root	= options[pane].buttonClass
            ,	class1	= root +"-pin"
            ,	class2	= class1 +"-"+ pane
            ,	UP1		= class1 + "-up"
            ,	UP2		= class2 + "-up"
            ,	DN1		= class1 + "-down"
            ,	DN2		= class2 + "-down"
            ;
            $Pin
            .attr("pin", doPin ? "down" : "up") // logic
            .attr("title", doPin ? "Un-Pin" : "Pin")
            .removeClass( doPin ? UP1 : DN1 ) 
            .removeClass( doPin ? UP2 : DN2 ) 
            .addClass( doPin ? DN1 : UP1 ) 
            .addClass( doPin ? DN2 : UP2 ) 
        ;
        };


        /*
 * ###########################
 * CREATE/RETURN BORDER-LAYOUT
 * ###########################
 */

        // init global vars
        var 
        $Container = $(this).css({
            overflow: "hidden"
        }) // Container elem
        ,	$Ps		= {} // Panes x4	- set in initPanes()
        ,	$Cs		= {} // Content x4	- set in initPanes()
        ,	$Rs		= {} // Resizers x4	- set in initHandles()
        ,	$Ts		= {} // Togglers x4	- set in initHandles()
        //	object aliases
        ,	c		= config // alias for config hash
        ,	cDims	= state.container // alias for easy access to 'container dimensions'
        ;

        // create the border layout NOW
        create();

        // return object pointers to expose data & option Properties, and primary action Methods
        return {
            options:		options			// property - options hash
            ,	
            state:			state			// property - dimensions hash
            ,	
            panes:			$Ps				// property - object pointers for ALL panes: panes.north, panes.center
            ,	
            toggle:			toggle			// method - pass a 'pane' ("north", "west", etc)
            ,	
            open:			open			// method - ditto
            ,	
            close:			close			// method - ditto
            ,	
            hide:			hide			// method - ditto
            ,	
            show:			show			// method - ditto
            ,	
            resizeContent:	sizeContent		// method - ditto
            ,	
            sizePane:		sizePane		// method - pass a 'pane' AND a 'size' in pixels
            ,	
            resizeAll:		resizeAll		// method - no parameters
            ,	
            addToggleBtn:	addToggleBtn	// utility - pass element selector and 'pane'
            ,	
            addOpenBtn:		addOpenBtn		// utility - ditto
            ,	
            addCloseBtn:	addCloseBtn		// utility - ditto
            ,	
            addPinBtn:		addPinBtn		// utility - ditto
            ,	
            allowOverflow:	allowOverflow	// utility - pass calling element
            ,	
            resetOverflow:	resetOverflow	// utility - ditto
            ,	
            cssWidth:		cssW
            ,	
            cssHeight:		cssH
        };

    }
})( jQuery );